﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace RamDrive
{
	/// <summary>
	/// This class is here because "MemoryStream" requires contigous memory. This class is basically a MemoryStream
	/// that can use non-contiguous memory, and also cannot grow past its original length.
	/// </summary>
	public class HighMemoryStream : Stream
	{
		public const int SEGMENT_SIZE = 40960;

		public HighMemoryStream(Stream source)
		{
			byte[] buffer = new byte[HighMemoryStream.SEGMENT_SIZE];

			int readBytes = 0;

			while ((readBytes = source.Read(buffer, 0, buffer.Length)) > 0)
			{
				_length += readBytes;

				var segment = new byte[readBytes];

				Array.Copy(buffer, segment, readBytes);

				_segments.Add(segment);
			}
		}

		private long _position;
		private long _length;
		private List<byte[]> _segments = new List<byte[]>();

		public override bool CanRead
		{
			get { return true; }
		}

		public override bool CanSeek
		{
			get { return true; }
		}

		public override bool CanWrite
		{
			get { return true; }
		}

		public override void Flush()
		{
			return;
		}

		public override long Length
		{
			get { return (long)_length; }
		}

		public override long Position
		{
			get
			{
				return (long)_position;
			}
			set
			{
				lock (_segments)
				{
					if (value < 0)
					{
						_position = 0;
					}
					else if (value > _length)
					{
						_position = _length;
					}
					else
					{
						_position = (int)value;
					}
				}
			}
		}

		public override int Read(byte[] buffer, int offset, int count)
		{
			lock (_segments)
			{
				long remaining = count;
				if (remaining > _length - _position)
				{
					remaining = (_length - _position);
				}
				long readAmount = 0;

				while (remaining > 0)
				{
					int segmentIndex = (int)(_position / HighMemoryStream.SEGMENT_SIZE);
					int segmentStartIndex = (int)(_position % HighMemoryStream.SEGMENT_SIZE);

					var segment = _segments[segmentIndex];

					long amountToRead = Math.Min(remaining, segment.Length - segmentStartIndex);

					Array.Copy(segment, segmentStartIndex, buffer, offset + readAmount, amountToRead);

					_position += amountToRead;
					remaining -= amountToRead;
					readAmount += amountToRead;
				}

				return (int)readAmount;
			}
		}

		public override long Seek(long offset, SeekOrigin origin)
		{
			switch (origin)
			{
				case SeekOrigin.Begin:
					Position = offset;
					break;

				case SeekOrigin.End:
					Position = _length - offset;
					break;

				case SeekOrigin.Current:
					Position += offset;
					break;
			}

			return Position;
		}

		public override void SetLength(long value)
		{
			throw new NotImplementedException();
		}

		public override void Write(byte[] buffer, int offset, int count)
		{
			lock (_segments)
			{
				long remaining = count;
				if (remaining > _length - _position)
				{
					remaining = (_length - _position);
				}
				int readAmount = 0;

				while (remaining > 0)
				{
					int segmentIndex = (int)(_position / HighMemoryStream.SEGMENT_SIZE);
					int segmentStartIndex = (int)(_position % HighMemoryStream.SEGMENT_SIZE);

					var segment = _segments[segmentIndex];

					long amountToRead = Math.Min(remaining, segment.Length - segmentStartIndex);

					Array.Copy(buffer, offset + readAmount, segment, segmentStartIndex, amountToRead);

					_position += amountToRead;
					remaining -= amountToRead;
				}
			}
		}
	}
}
